package example;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.*;

// 自定义窗口类，创建对象，展示一个主窗口。
public class MainFrame extends JFrame {
    // 定义一个常量设置图片位置
    private static final String IMAGE_PATH = "day09-stone-maze/src/image/";

    // 准备一个数组，用于存储数字色块的行列位置：4行4列
    private int[][] imageData = {
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };

    // 定义一个二维数组，用于存储最终游戏成功的数据顺序
    private int[][] winData = new int[][]{
            {1, 2, 3, 4},
            {5, 6, 7, 8},
            {9, 10, 11, 12},
            {13, 14, 15, 0}
    };

    // 定义两个整数变量记录当前空白色块的位置
    private int row; // 行索引
    private int col; // 列索引
    private int count; // 统计累计的步数
    private int minCount; // 最少步数

    public MainFrame() {
        minCount = readFileScore();

        // 1.调用一个初始化方法，初始化窗口大小信息等
        initFrame();
        // 4.打乱数组色块的顺序，在展示图片
        initRandomArray();
        // 2.初始化界面：展示数字色块
        initImage();
        // 3.初始化系统菜单：点击弹出菜单信息是系统退出，重启游戏
        initMenu();

        // 5.给当前窗口绑定上下左右按键事件
        initKeyPressEvent();

        // 设置窗口的显示
        this.setVisible(true);
    }

    private void initKeyPressEvent() {
        // 给当前窗口绑定上下左右按键事件
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                // 获取当前按键的的编号
                int keyCode = e.getKeyCode();
                // 判断这个编号是否是上下左右的按键
                switch (keyCode) {
                    case KeyEvent.VK_UP:
                        // 用户按了上键，图片向上移动
                        switchAndMove(Direction.UP);
                        break;
                    case KeyEvent.VK_DOWN:
                        // 用户按了下键，图片向下移动
                        switchAndMove(Direction.DOWN);
                        break;
                    case KeyEvent.VK_LEFT:
                        // 用户按了左键，图片向左移动
                        switchAndMove(Direction.LEFT);
                        break;
                    case KeyEvent.VK_RIGHT:
                        // 用户按了右键，图片向右移动
                        switchAndMove(Direction.RIGHT);
                        break;
                }
            }
        });
    }

    // 控制数据交换和图片移动
    private void switchAndMove(Direction direction) {
        // 根据图片的放向，在控制图片移动
        switch (direction) {
            case UP:
                // 上交换的条件是行必须 < 3，然后才开始交换.
                if (row < imageData.length - 1) {
                    // 当前空白色块位置：rol  col
                    // 需要被交换的位置：row + 1  col
                    int temp = imageData[row][col];
                    imageData[row][col] = imageData[row + 1][col];
                    imageData[row + 1][col] = temp;
                    // 更新当前空白色块的位置了。
                    row++;
                    count++;
                }
                break;
            case DOWN:
                if (row > 0) {
                    // 当前空白色块位置：rol  col
                    // 需要被交换的位置：row - 1  col
                    int temp = imageData[row][col];
                    imageData[row][col] = imageData[row - 1][col];
                    imageData[row - 1][col] = temp;
                    // 更新当前空白色块的位置了。
                    row--;
                    count++;
                }
                break;
            case LEFT:
                // 左交换的条件是空白色块的列必须小于3
                if (col < imageData.length - 1) {
                    // 当前空白色块位置：rol  col
                    // 需要被交换的位置：row   col + 1
                    int temp = imageData[row][col];
                    imageData[row][col] = imageData[row][col + 1];
                    imageData[row][col + 1] = temp;
                    // 更新当前空白色块的位置了。
                    col++;
                    count++;
                }
                break;
            case RIGHT:
                if (col > 0) {
                    // 当前空白色块位置：rol  col
                    // 需要被交换的位置：row   col - 1
                    int temp = imageData[row][col];
                    imageData[row][col] = imageData[row][col - 1];
                    imageData[row][col - 1] = temp;
                    // 更新当前空白色块的位置了。
                    col--;
                    count++;
                }
                break;
        }
        // 重新刷新界面
        initImage();
    }

    private void initRandomArray() {
        // 打乱二维数组中的元素顺序
        for (int i = 0; i < imageData.length; i++) {
            for (int j = 0; j < imageData.length; j++) {
                // 随即两个行列位置，让这两个位置交换
                int i1 = (int) (Math.random() * imageData.length);
                int j1 = (int) (Math.random() * imageData.length);

                int i2 = (int) (Math.random() * imageData.length);
                int j2 = (int) (Math.random() * imageData.length);

                // 交换数组中两个元素的位置
                int temp = imageData[i1][j1];
                imageData[i1][j1] = imageData[i2][j2];
                imageData[i2][j2] = temp;
            }
        }

        // 定位空白色块的位置
        // 遍历二位数值中的数据，找到0的位置，即当前的空白色块的位置
        OUT:
        for (int i = 0; i < imageData.length; i++) {
            for (int j = 0; j < imageData.length; j++) {
                if (imageData[i][j] == 0) {
                    // 定位到空白色块的位置
                    row = i;
                    col = j;
                    // 如果直接些break ，会直接跳出内层循环，不会跳出外层循环
                    // 所以在最外层循环开始之前，定义一个标签，在内部循环可以直接跳出到 OUT 标签的位置处
                    break OUT;
                }
            }
        }
    }

    private void initMenu() {
        JMenuBar menuBar = new JMenuBar(); // 创建一个菜单条
        JMenu menu = new JMenu("系统"); // 创建一个菜单
        JMenuItem exitJi = new JMenuItem("退出");
        menu.add(exitJi); // 添加一个菜单项
        // 给菜单项添加点击事件
        exitJi.addActionListener(e -> {
            dispose(); // 销毁窗口
        });

        // 添加一个菜单，重启
        JMenuItem restartJi = new JMenuItem("重启");
        menu.add(restartJi);
        restartJi.addActionListener(e -> {
            // 重启游戏: 重新打乱二维数组的顺序，在刷新界面
            count = 0; // 重置步数
            initRandomArray();
            initImage();
        });
        menuBar.add(menu); // 添加菜单到菜单条中
        this.setJMenuBar(menuBar);
    }

    private void initImage() {
        // 先清空窗口上的全部图层
        this.getContentPane().removeAll();

        // 刷新界面时，可以给界面显示步数
        // 给窗口添加一个展示文字的组件
        JLabel countTxt = new JLabel("当前步数：" + count);
        countTxt.setBounds(20, 20, 100, 20);
        // 把文字展示成红色
        countTxt.setForeground(Color.RED);
        // 加粗
        countTxt.setFont(new Font("楷体", Font.BOLD, 15));
        this.add(countTxt);

        // 判断是否是第一次玩游戏，如果是展示目前没有历史步数 通过minCount判断
        if (minCount != 0) {
            // 刷新界面时，可以给界面显示步数
            JLabel countTxt2 = new JLabel("最少步数：" + minCount);
            countTxt2.setBounds(300, 20, 130, 20);
            // 把文字展示成红色
            countTxt2.setForeground(Color.WHITE);
            // 加粗
            countTxt2.setFont(new Font("楷体", Font.BOLD, 15));
            this.add(countTxt2);
        } else {
            // 刷新界面时，可以给界面显示步数
            JLabel countTxt2 = new JLabel("没有历史步数");
            countTxt2.setBounds(300, 20, 130, 20);
            // 把文字展示成红色
            countTxt2.setForeground(Color.WHITE);
            // 加粗
            countTxt2.setFont(new Font("楷体", Font.BOLD, 15));
            this.add(countTxt2);
        }


        // 判断是否赢了
        if (isWin()) {
            // 展示胜利的界面
            JLabel label = new JLabel(new ImageIcon(IMAGE_PATH + "win.png"));
            label.setBounds(124, 230, 266, 88);
            this.add(label);

            // 读取文件中的最少步数，判断是否需要更新
            int fileMinCount = readFileScore(); // fileMinCount表示当前的游戏步数
            // 判断这个步数是否为0，如果是0说明你是第一次玩游戏，直接把当前游戏步数记录进去即可
            if (fileMinCount == 0 || count < fileMinCount) {
                writeFileScore(count);
            }
        }

        // 1.展示一个行列矩阵的图片色块依次铺满窗口（4*4）
        for (int i = 0; i < imageData.length; i++) {
            for (int j = 0; j < imageData.length; j++) {
                // 获取图片名称
                String imageName = imageData[i][j] + ".png";
                // 2.创建一个JLabel对象，设置图片给他显示
                JLabel label = new JLabel();
                // 3.设置图片到label对象中去
                label.setIcon(new ImageIcon(IMAGE_PATH + imageName));
                // 4.设置图片位置展示出来
                label.setBounds(20 + j * 100, 60 + i * 100, 100, 100);
                // 5.把这个图片展示到窗口上去
                this.add(label);
            }
        }

        // 设置窗口的背景图片
        JLabel background = new JLabel(new ImageIcon(IMAGE_PATH + "background.png"));
        background.setBounds(0, 0, 450, 484);
        this.add(background);

        // 刷新新图层,重新绘制
        this.repaint();
    }

    // 把当前最少步数写入到文件中去更新
    private void writeFileScore(int count) {
        try (
                FileWriter fw = new FileWriter("day13-stone-maze-file/src/score.txt");
                BufferedWriter bw = new BufferedWriter(fw);
        ) {
            // 把步数写入到文件中
            bw.write(count + " ");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    // 读取score.txt文件中的最小步数
    private int readFileScore() {
        try (
                FileReader fr = new FileReader("day13-stone-maze-file/src/score.txt");
                BufferedReader br = new BufferedReader(fr)
        ) {
            // 读取文件中最小的步数
            String line = br.readLine();
            return Integer.parseInt(line); // 把读取到的数据转成整数返回去
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    private boolean isWin() {
        // 判断游戏的二维数组和预设的二维数组是否一致，只要有一个不一致，就返回false
        for (int i = 0; i < imageData.length; i++) {
            for (int j = 0; j < imageData.length; j++) {
                // 如果当前二维数组中的元素和预设的二维数组中的元素不一致，就返回false
                if (imageData[i][j] != winData[i][j]) {
                    return false;
                }
            }
        }
        // 如果循环结束，没有返回false，就返回true，表示游戏胜利了
        return true;
    }

    private void initFrame() {
        // 设置窗口的标题
        this.setTitle("石子迷阵 v1.0");
        // 设置窗口的大小
        this.setSize(465, 580);
        // 设置窗口的关闭方式
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 设置窗口的位置
        this.setLocationRelativeTo(null);
        // 设置布局方式为绝对位置定位
        this.setLayout(null);
    }
}
